//********************************************************************************************************
// Product Name: MapWindow.dll Alpha
// Description:  Low level interfaces that allow separate components to use objects that are defined
//               in completely independant libraries so long as they satisfy the basic interface requirements.
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either express or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Original Code is MapWindow.Interfaces
//
// The Initial Developer of this Original Code is Ted Dunsford. Created in October, 2007.
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
//
//********************************************************************************************************
using System;
using System.Collections.Generic;
using System.Text;
using MapWindow.Data;
using System.Drawing;
using MapWindow.Main.EventArguments;
using MapWindow.Geometries;
using System.ComponentModel;
namespace MapWindow.Drawing
{
    /// <summary>
    /// This contains the shared symbolizer properties between Points, Lines and Polygons.  
    /// </summary>
    public class FeatureSymbolizer : Object, IFeatureSymbolizer
    {
        #region Variables

        /// <summary>
        /// The Default Symbolizer for this feature layer
        /// </summary>
        protected IFeatureSymbolizer _DefaultProvider;

        /// <summary>
        /// The default color of the borders
        /// </summary>
        protected System.Drawing.Color _BorderColor;

        /// <summary>
        /// The pen used for the borders
        /// </summary>
        protected System.Drawing.Pen _BorderPen;

        /// <summary>
        /// The float width of the border pen.  The Scale mode
        /// changes whether this is in pixels or world units.
        /// </summary>
        protected float _BorderWidth;

        /// <summary>
        /// Boolean, controls whether the border will be drawn
        /// </summary>
        protected TrueFalse _BorderVisible;

        /// <summary>
        /// Boolean, If this is true, then any future drawing will be canceled
        /// </summary>
        protected bool _CancelDrawing;

        /// <summary>
        /// Controls the color of the FillBrush
        /// </summary>
        protected System.Drawing.Color _FillColor;
        
        /// <summary>
        /// A System.Drawing.Brush to use when filling the features
        /// </summary>
        protected System.Drawing.Brush _FillBrush;

        /// <summary>
        /// Boolean, set to true while actively drawing
        /// </summary>
        protected bool _IsBusy;

        /// <summary>
        /// Boolean.  For individual shapes, this property allows the default
        /// layer properties to be used.
        /// </summary>
        protected bool _IsEmpty; // If any properties are directly set, this becomes false.
        
        /// <summary>
        /// Not implemented, but will ultimately control transparency
        /// </summary>
        protected float _Opacity;

        /// <summary>
        /// Controls whether units like width or pointsize are in geographic or world units
        /// </summary>
        protected ScaleModes _ScaleMode;
        
        /// <summary>
        /// Determines whether the features drawn by this symbolizer will actually be drawn
        /// </summary>
        protected bool _Visible;

        /// <summary>
        /// A string name to keep track of the symbolizer
        /// </summary>
        protected string _Name;

        #endregion

        #region constructors

        /// <summary>
        /// This constructor takes on some default values, and assumes that it
        /// has no other underlying symblizer to reference.
        /// </summary>
        public FeatureSymbolizer()
        {
            _IsBusy = false;
            _BorderPen = new Pen(new SolidBrush(Color.Black));
            _BorderColor = Color.Black;
            _BorderWidth = 1f;
            _BorderVisible = TrueFalse.True;
           
            // Use the property to also set FillColor
            FillBrush = new SolidBrush(RandomHue(0.8, 0.8));
            
            _IsEmpty = false;
            _Opacity = 1f;
            _Visible = true; // This is boolean and should be true by default
        }


        /// <summary>
        /// As an example, a MapPointLayer has symbols that can act as the DefaultProvider
        /// in the event that one of the symbols is not defined.  If the symbol characteristic
        /// for an individual point isn't specified, it can check the Layer, but only if the
        /// layer is passed in as the default.  You could just as easilly define the 0th point
        /// to be a "master", or have a stand alone Symbolizer for this purpose.
        /// </summary>
        /// <param name="defaultProvider"></param>
        public FeatureSymbolizer(IFeatureSymbolizer defaultProvider)
        {
            _DefaultProvider = defaultProvider;
            Clear();
           
        }

        #endregion

        #region Methods

        /// <summary>
        /// The clear function will be overridden by subclasses to do this and more
        /// </summary>
        public virtual void Clear()
        {
            _IsBusy = false;
            _BorderColor = System.Drawing.Color.Empty;
            _BorderWidth = 0f;
            _BorderVisible = TrueFalse.True;
            _BorderPen = null;
            _FillColor = System.Drawing.Color.Empty;
            _FillBrush = null;
            _IsEmpty = true;
            _Opacity = -1f;
            _Visible = true; // This is boolean and should be true by default
        }

        /// <summary>
        /// This will copy the characteristics that are shared between points, lines and polyons
        /// </summary>
        /// <param name="copyFrom">Any of the valid IFeatureSymbolizers like Point, Line or Polygon</param>
        public void Copy(IFeatureSymbolizer copyFrom)
        {
            BorderPen = copyFrom.BorderPen;
            BorderColor = copyFrom.BorderColor;
            BorderWidth = copyFrom.BorderWidth;
            if (copyFrom.BorderVisible == true)
            {
                _BorderVisible = TrueFalse.True;
            }
            else
            {
                _BorderVisible = TrueFalse.False;
            }
            _FillBrush = copyFrom.FillBrush;
            _FillColor = copyFrom.FillColor;

        }

        /// <summary>
        /// Draws the renderable item onto the specified graphics object.  No changes
        /// will be made to the graphics object for scaling or positioning and it is
        /// assumed that any clip-handling has been handled beforehand.
        /// </summary>
        /// <param name="g">The System.Drawing.Graphics surface to draw on.</param>
        public virtual void Draw(System.Drawing.Graphics g)
        {
            Draw(g, Rectangle.Empty, Rectangle.Empty);
        }

        /// <summary>
        /// Draws the renderable item onto the specified graphics object.  If the
        /// clipRectangle doesn't intersect the target, no drawing will take place.
        /// </summary>
        /// <param name="g">The System.Drawing.Graphics surface to draw on.</param>
        /// <param name="clipRectangle">The bounds within which to draw</param>
        public virtual void Draw(System.Drawing.Graphics g, Rectangle clipRectangle)
        {
            Draw(g, clipRectangle, Rectangle.Empty);
        }

      
        /// <summary>
        /// In this case will fill the specified target as though it were a rectangular
        /// polygon, using the symbology from this feature.
        /// </summary>
        /// <param name="g">The System.Drawing.Graphics surface to draw on.</param>
        /// <param name="clipRectangle">The bounds within which to draw</param>
        /// <param name="target">This specifies the pixel "Fit To" rectangle</param>
        public virtual void Draw(System.Drawing.Graphics g, Rectangle clipRectangle, Rectangle target)
        {
            _IsBusy = true;
            
            if (_CancelDrawing == true)
            {
                OnPaintCancelled(new Exception("CancelDrawing was true"), g, clipRectangle, target);
                return;
            }
            if (_Visible == false)
            {
                OnAfterPainting(g, clipRectangle, target);
                return;
            }
            
            if (clipRectangle != Rectangle.Empty)
            {
                g.IntersectClip(clipRectangle);
            }
            lock (FillBrush)
            {
                g.FillRectangle(FillBrush, target);
                g.DrawRectangle(BorderPen, target);
            }
        }
        
        #endregion


        #region Properties

        /// <summary>
        /// Gets or sets the color of the border, or outline around the symbol
        /// </summary>
        [CategoryAttribute("Appearance"), DescriptionAttribute("Gets or sets the color of the border, or outline around this symbol.")]
        public virtual System.Drawing.Color BorderColor
        {
            get
            {
                if (_BorderColor == Color.Empty)
                {
                    if (_DefaultProvider != null)
                    {
                        return _DefaultProvider.BorderColor;
                    }
                    return Color.Black;
                }
                return _BorderColor;

            }
            set
            {
                // If they want opacity, they can set it after setting the color.  If they don't want a 
                // part of the symbol colored, they can set that property AFTER setting opacity to effectively negate
                // the opacity setting for that part, or control it through the Alpha channel.
               
                _BorderColor = value;
                _BorderPen.Color = value;
                _IsEmpty = false;
            }
        }

        

        /// <summary>
        /// Gets or sets the pen to use with the this point to draw the border around this symbol.
        /// Setting this will also reset the BorderColor proprety.
        /// </summary>
        [CategoryAttribute("Appearance"), DescriptionAttribute("Gets or sets the pen to use with the this point to draw the border.")]
        public virtual System.Drawing.Pen BorderPen
        {
            get
            {
                if (_BorderPen == null)
                {
                    if (_DefaultProvider != null)
                    {
                        return _DefaultProvider.BorderPen;
                    }
                    return new Pen(Color.Black);
                }
                return _BorderPen;

            }
            set
            {
                _BorderPen = value;
                _BorderWidth = value.Width;
                _BorderColor = value.Color;
                _IsEmpty = false;
            }
        }

        /// <summary>
        /// Gets or sets a boolean value that determines whether or not a border will be drawn around this point.
        /// </summary>
        [CategoryAttribute("Appearance"), DescriptionAttribute("Gets or sets a boolean value that determines whether or not the default points for this layer will draw a border.")]
        public virtual bool BorderVisible
        {
            get
            {

                if (_BorderVisible == TrueFalse.Empty)
                {
                    if (_DefaultProvider != null)
                    {
                        return _DefaultProvider.BorderVisible;
                    }
                    return true;
                }
                if (_BorderVisible == TrueFalse.True)
                {
                    return true;
                }
                return false;
            }
            set
            {
                if (value == true)
                {
                    _BorderVisible = TrueFalse.True;
                    _IsEmpty = false;
                }
                else
                {
                    _BorderVisible = TrueFalse.False;
                    _IsEmpty = false;
                }

            }
        }

        /// <summary>
        /// Gets or sets the float thickness for the border of this point.
        /// Setting this property will also affect the BorderPen property.
        /// If this is negative or 0, the default layer thickness is used.
        /// </summary>
        [CategoryAttribute("Appearance"), DescriptionAttribute("Gets or sets the float thickness for the border or outline around this symbol.")]
        public virtual float BorderWidth
        {
            get
            {
                if (_BorderWidth <= 0f)
                {
                    if (_DefaultProvider != null)
                    {
                        return _DefaultProvider.BorderWidth;
                    }
                    return 1f;
                }
                return _BorderWidth;

            }
            set
            {
                _BorderWidth = value;
                _BorderPen.Width = value;
                _IsEmpty = false;
            }
        }


        /// <summary>
        /// This propogates the cancel message down through the drawing levels
        /// to ensure that the current drawing action is terminated as soon
        /// as possible in order to exit gracefully.
        /// </summary>
        public virtual bool CancelDrawing
        {
            get { return _CancelDrawing; }
            set
            {
                _CancelDrawing = value;
            }
        }

        /// <summary>
        /// Gets or sets the solid color to be used for filling the symbol.
        /// This will also change the FillBrush to a solid brush of the specified color.
        /// </summary>
        [CategoryAttribute("Appearance"), DescriptionAttribute("Gets or sets the solid color to be used for filling the symbol.")]
        public virtual System.Drawing.Color FillColor
        {
            get
            {
                if (_FillColor == Color.Empty)
                {
                    if (_DefaultProvider != null)
                    {
                        return _DefaultProvider.FillColor;
                    }
                }
                return _FillColor;
            }
            set
            {
                _FillColor = value;
                _FillBrush = new System.Drawing.SolidBrush(value);
                _IsEmpty = false;
            }
        }

        /// <summary>
        /// Gets or sets the System.Drawing.Brush to be used when filling this point.
        /// Setting this value will also change the FillColor property, but only if
        /// the brush is a System.Drawing.SolidBrush.
        /// </summary>
        [CategoryAttribute("Appearance"), DescriptionAttribute("Gets or sets the System.Drawing.Brush to be used when filling this symbol.")]
        public virtual System.Drawing.Brush FillBrush
        {
            get
            {
                if (_FillBrush == null)
                {
                    if (_DefaultProvider != null)
                    {
                        return _DefaultProvider.FillBrush;
                    }
                }
                return _FillBrush;

            }
            set
            {
                _FillBrush = value;
                if (value is System.Drawing.SolidBrush)
                {
                    _FillColor = ((System.Drawing.SolidBrush)value).Color;

                }
                _IsEmpty = false;
            }
        }

        /// <summary>
        /// Boolean, true if the drawing code is executing
        /// </summary>
        [CategoryAttribute("Behavior"), DescriptionAttribute("Gets whether or not this symbol is actively drawing.")]
        public virtual bool IsBusy
        {
            get { return _IsBusy; }
        }

        /// <summary>
        /// Gets a boolean that is true if no properties have been set for this symbolizer, and all the properties
        /// will just use default values
        /// </summary>
        public virtual bool IsEmpty
        {
            get { return _IsEmpty; }
        }

        /// <summary>
        /// Gets or sets a string name to help identify this Symbolizer
        /// </summary>
        public virtual string Name
        {
            get { return _Name; }
            set { _Name = value; }
        }

        /// <summary>
        /// Gets or sets a float value from 0 to 1 where 0 is completely transparent
        /// and 1 is completely solid.  Setting an alpha of a specific feature, like
        /// FillColor, to something less than 255 will control that feature's transparency
        /// without affecting the others.  The final transparency of the feature will
        /// be that alpha multiplied by this value and rounded to the nearest byte.
        /// </summary>
        public virtual float Opacity
        {
            get 
            {
                if (_Opacity < 0f)
                {
                    if (_DefaultProvider != null)
                    {
                        return _DefaultProvider.Opacity;
                    }
                    return 1; // The default is 1 for completely solid
                }
                return _Opacity;
            }
            set
            {
                float val = value;
                if (val > 1) val = 1;
                if (val < 0) val = 0;
                byte A = Convert.ToByte(255 * val);
                FillColor = Color.FromArgb(A, FillColor.R, FillColor.G, FillColor.B);
                BorderColor = Color.FromArgb(A, BorderColor.R, BorderColor.G, BorderColor.B);
                _Opacity = value;
            }
        }

       


        /// <summary>
        /// Gets or Sets a ScaleModes enumeration that determines whether non-coordinate drawing
        /// properties like width or size use pixels or world coordinates.  If pixels are
        /// specified, a back transform is used to approximate pixel sizes. 
        /// </summary>
        public virtual ScaleModes ScaleMode
        {
            get
            {
                if (_ScaleMode == ScaleModes.Empty)
                {
                    if (_DefaultProvider != null)
                    {
                        return _DefaultProvider.ScaleMode;
                    }
                    return ScaleModes.Pixels;
                }
                return _ScaleMode;
            }
            set { _ScaleMode = value; }
        }

        /// <summary>
        /// Gets or sets a boolean indicating whether or not this specific feature should be drawn.
        /// </summary>
        [CategoryAttribute("Appearance"), DescriptionAttribute("Gets or sets a boolean indicating whether or not this specific MapPoint should be drawn.")]
        public virtual bool Visible
        {
            get
            {
                return _Visible;
            }
            set
            {
                _Visible = value;
                _IsEmpty = false;
            }
        }


        

        #endregion

        #region Events

        /// <summary>
        /// Fires before the paint event.  If the cancel property of this event is set to true,
        /// the paint event will be canceled.  The rect property is the "fit-to" property for
        /// this event.
        /// </summary>
        public virtual event EventHandler<PaintCancel> BeforePainting;

        /// <summary>
        /// Fires the BeforeDrawing Event
        /// </summary>
        /// <param name="g"></param>
        /// <param name="clipRect"></param>
        /// <param name="target"></param>
        /// <returns></returns>
        protected virtual bool OnBeforePainting(System.Drawing.Graphics g, System.Drawing.Rectangle clipRect, System.Drawing.Rectangle target)
        {
            if (BeforePainting != null)
            {
                PaintCancel e = new PaintCancel(g, clipRect, target);
                BeforePainting(this, e);
                if (e.Cancel == true) return true;
            }
            return false;
        }

        /// <summary>
        /// Fires after the paint event is complete.  The "clipRectangle" was actually the 
        /// "Fit-To" rectangle that the frame is drawn within.
        /// </summary>
        public virtual event EventHandler<PaintCompleted> AfterPainting;

        /// <summary>
        /// The drawing method finished, and exited normally.
        /// </summary>
        /// <param name="g">The System.Drawing.Graphics surface used by the draw method.</param>
        /// <param name="clipRect">The System.Drawing.Rectangle defining the area to actually perform drawing</param>
        /// <param name="target">The destination rectangle for the System.Drawing.Rectangle where the result should be drawn</param>
        protected virtual void OnAfterPainting(System.Drawing.Graphics g, System.Drawing.Rectangle clipRect, System.Drawing.Rectangle target)
        {
            if (AfterPainting != null)
            {
                AfterPainting(this, new PaintCompleted(g, clipRect, target));
            }
        }

        /// <summary>
        /// Internal Method to fire the Cancel Event.  This caries the same basic information that the 
        /// DrawingComplete method, plus an exception if any as well as a boolean indicating the draw method was cancelled.
        /// </summary>
        /// <param name="Error">A System.Exception showing the error that caused the cancelation</param>
        /// <param name="g">A System.Drawing.Graphics surface that drawing was taking place on</param>
        /// <param name="clipRect">The ClipRectangle or Rectangle.Empty</param>
        /// <param name="target">The Destination Rectangle or Rectangle.Empty</param>
        protected virtual void OnPaintCancelled(System.Exception Error, System.Drawing.Graphics g, System.Drawing.Rectangle clipRect, System.Drawing.Rectangle target)
        {
            if (AfterPainting != null)
            {
                AfterPainting(this, new PaintCompleted(Error, true, g, clipRect, target));
            }
        }



        #endregion

        /// <summary>
        /// Calculates a color with a random hue that matches the specified Saturation
        /// and Lightness
        /// </summary>
        /// <param name="saturation">If the random hue is blended with a gray background, high saturation is more coloful, low saturation is more like the background.</param>
        /// <param name="lightness">High values are white, low values are black.</param>
        /// <returns></returns>
        public Color RandomHue(double saturation, double lightness)
        {
            // This should automatically calculate a random value from 0 to 1
            Random rnd = new Random(DateTime.Now.Millisecond);
      
            double hue = (double)rnd.Next(0, 255) / (double)256; 
            return HSL_to_RGB(hue, saturation, lightness);
        }
        /// <summary> 
        /// Converts a colour from HSL to RGB 
        /// </summary>
        /// <remarks>Adapted from the algoritm in Foley and Van-Dam</remarks> 
        /// <param name="H">The double hue ranging from 0 to 1</param>
        /// <param name="L">The double lightness ranging from 0 to 1</param>
        /// <param name="S">The double saturation ranging from 0 to 1</param>
        /// <returns>A Color structure containing the equivalent RGB values</returns> 
        public Color HSL_to_RGB(double H, double S, double L)
        {
            if (H > 1) H = 1;
            if (S > 1) S = 1;
            if (L > 1) L = 1;
            if (H < 0) H = 0;
            if (S < 0) S = 0;
            if (L < 0) L = 0;

            double r = 0, g = 0, b = 0;
            double temp1, temp2;
            if (L == (byte)0)
            {
                r = g = b = 0;
            }
            else
            {
                if (S == (byte)0)
                {
                    r = g = b = L;
                }
                else
                {
                    temp2 = ((L <= 0.5) ? L * (1.0 + S) : L + S - (L * S));
                    temp1 = 2.0 * L - temp2;
                    double[] t3 = new double[] { H + 1.0 / 3.0, H, H - 1.0 / 3.0 };
                    double[] clr = new double[] { 0, 0, 0 };
                    for (int i = 0; i < 3; i++)
                    {
                        if (t3[i] < 0)
                            t3[i] += 1.0;
                        if (t3[i] > 1)
                            t3[i] -= 1.0;
                        if (6.0 * t3[i] < 1.0)
                            clr[i] = temp1 + (temp2 - temp1) * t3[i] * 6.0;
                        else if (2.0 * t3[i] < 1.0)
                            clr[i] = temp2;
                        else if (3.0 * t3[i] < 2.0)
                            clr[i] = (temp1 + (temp2 - temp1) * ((2.0 / 3.0) - t3[i]) * 6.0);
                        else
                            clr[i] = temp1;
                    }
                    r = clr[0];
                    g = clr[1];
                    b = clr[2];
                }
            }
            return Color.FromArgb((int)(255 * r), (int)(255 * g), (int)(255 * b));
        } 


     
    }
}
